/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu. 
 * 
 * Copyright (C) 2005-2010  Dirk Beyer
 * 
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 * 
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */

package ccvisu;

import java.io.PrintWriter;
import java.util.List;

import ccvisu.Options.Option;

/*****************************************************************
 * Writer for layouts in VRML format.
 * @version  $Revision: 1.38 $; $Date: 2007/12/15 01:20:51 $
 * @author   Dirk Beyer
 *****************************************************************/
public class WriterDataGraphicsVRML extends WriterDataGraphics {

  /**
   * Constructor.
   */
  public WriterDataGraphicsVRML(PrintWriter out, GraphData graph, Options opt) {
    super(out, graph, opt);
  }

  /*****************************************************************
   * Writes the layout in graphics format VRML.
   *****************************************************************/
  @Override
  public void write() {
    int size = (int) (1000 * Option.scalePos.getFloat());

    // Header.
    out
        .print("#VRML V2.0 utf8 "
               + WriterData.endl
               + "# "
               + WriterData.endl
               + "# Generated by "
               + Options.toolDescription()
               + WriterData.endl
               + "#   "
               + Options.currentDateTime()
               + " "
               + WriterData.endl
               + "  "
               + WriterData.endl
               + " PROTO TRANSFORM_Sphere [ "
               + WriterData.endl
               + "    field SFVec3f translation 0 0 0 "
               + WriterData.endl
               + "    field SFVec3f scale 1 1 1 "
               + WriterData.endl
               + "    field SFColor diffuseColor 0.5 0.5 0.5 "
               + WriterData.endl
               + "    field SFString name \"\" "
               + WriterData.endl
               + "    field SFVec3f textTranslate 0 0 1 "
               + WriterData.endl
               + "    field SFFloat textSize "
               + Option.fontSize.getInt()
               + WriterData.endl
               + " ] { "
               + WriterData.endl
               + "    Transform { "
               + WriterData.endl
               + "        translation IS translation "
               + WriterData.endl
               + "        children [ "
               + WriterData.endl
               + "            Transform { "
               + WriterData.endl
               + "                scale IS scale "
               + WriterData.endl
               + "                children [ "
               + WriterData.endl
               + "                    DEF sensor0 TouchSensor {} "
               + WriterData.endl
               + "                    Shape { "
               + WriterData.endl
               + "                        geometry Sphere { radius 0.5 } "
               + WriterData.endl
               + "                        appearance Appearance { "
               + WriterData.endl
               + "                            material Material { "
               + WriterData.endl
               + "                                ambientIntensity 0.3 "
               + WriterData.endl
               + "                                diffuseColor IS diffuseColor "
               + WriterData.endl
               + "                            } "
               + WriterData.endl
               + "                        } "
               + WriterData.endl
               + "                    } "
               + WriterData.endl
               + "                ] "
               + WriterData.endl
               + "            } "
               + WriterData.endl
               + "            Billboard { "
               + WriterData.endl
               + "                axisOfRotation 0 0 0 "
               + WriterData.endl
               + "                children [ "
               + WriterData.endl
               + "                    Transform { "
               + WriterData.endl
               + "                        translation IS textTranslate "
               + WriterData.endl
               + "                        children [ "
               + WriterData.endl
               + "                            Shape { "
               + WriterData.endl
               + "                                geometry DEF label0 Text { string [ \"\" ] fontStyle FontStyle { family \"SANS\" size IS textSize } }"
               + WriterData.endl
               + "                                appearance Appearance { "
               + WriterData.endl
               + "                                    material Material { "
               + WriterData.endl
               + "                                        ambientIntensity 0.3 "
               + WriterData.endl
               + "                                        diffuseColor 0 0 0 "
               + WriterData.endl
               + "                                    } "
               + WriterData.endl
               + "                                } "
               + WriterData.endl
               + "                            } "
               + WriterData.endl
               + "                        ] "
               + WriterData.endl
               + "                    } "
               + WriterData.endl
               + "                ] "
               + WriterData.endl
               + "            } "
               + WriterData.endl
               + "            DEF script0 Script { "
               + WriterData.endl
               + "                eventIn SFBool isOver "
               + WriterData.endl
               + "                eventIn SFBool touchTime "
               + WriterData.endl
               + "                eventOut MFString trans "
               + WriterData.endl
               + "                field SFBool labelDisplayed FALSE "
               + WriterData.endl
               + "                field SFString label IS name "
               + WriterData.endl
               + "                url [ \"javascript: "
               + WriterData.endl
               + "                    function isOver(value) { "
               + WriterData.endl
               + "                        if (value) { "
               + WriterData.endl
               + "                            trans[0] = label; "
               + WriterData.endl
               + "                        } else { "
               + WriterData.endl
               + "                            if (!labelDisplayed) trans[0] = ''; "
               + WriterData.endl + "                        } "
               + WriterData.endl + "                    } " + WriterData.endl
               + "                    function touchTime() { "
               + WriterData.endl
               + "                        if (labelDisplayed) { "
               + WriterData.endl
               + "                            trans[0] = ''; "
               + WriterData.endl
               + "                            labelDisplayed = false; "
               + WriterData.endl + "                        } else { "
               + WriterData.endl
               + "                            trans[0] = label; "
               + WriterData.endl
               + "                            labelDisplayed = true; "
               + WriterData.endl);

    if (Option.openURL.getBool()) {
      out
          .println("                            Browser.loadURL(new MFString(label), new MFString('target=DUMMY'));");
    }

    out
        .print("                        } "
               + WriterData.endl
               + "                    }\" "
               + WriterData.endl
               + "                ] "
               + WriterData.endl
               + "            } "
               + WriterData.endl
               + "        ] "
               + WriterData.endl
               + "    } "
               + WriterData.endl
               + "    ROUTE sensor0.isOver TO script0.isOver "
               + WriterData.endl
               + "    ROUTE sensor0.touchTime TO script0.touchTime "
               + WriterData.endl
               + "    ROUTE script0.trans TO label0.set_string "
               + WriterData.endl
               + " } "
               + WriterData.endl
               + WriterData.endl

               + " PROTO TRANSFORM_Cylinder [ "
               + WriterData.endl
               + "    field SFVec3f translation 0 0 0 "
               + WriterData.endl
               + "    field SFRotation rotation 0 0 0 0"
               + WriterData.endl
               + "    field SFFloat height 1 "
               + WriterData.endl
               + "    field SFColor diffuseColor 0.5 0.5 0.5 "
               + WriterData.endl
               + "    field SFString name \"\" "
               + WriterData.endl
               + "    field SFVec3f textTranslate 0 0 1 "
               + WriterData.endl
               + "    field SFFloat textSize "
               + Option.fontSize.getInt()
               + WriterData.endl
               + " ] { "
               + WriterData.endl
               + "     Transform { "
               + WriterData.endl
               + "         translation IS translation "
               + WriterData.endl
               + "         children [ "
               + WriterData.endl
               + "         Transform { "
               + WriterData.endl
               + "             rotation IS rotation "
               + WriterData.endl
               + "             children [ "
               + WriterData.endl
               + "                 DEF sensor0 TouchSensor {} "
               + WriterData.endl
               + "                 Shape { "
               + WriterData.endl
               + "                     geometry Cylinder { "
               + WriterData.endl
               + "                         height IS height "
               + WriterData.endl
               + "                         radius 0.5  "
               + WriterData.endl
               + "                         top FALSE  "
               + WriterData.endl
               + "                         side TRUE  "
               + WriterData.endl
               + "                         bottom FALSE "
               + WriterData.endl
               + "                     } "
               + WriterData.endl
               + "                         appearance Appearance { "
               + WriterData.endl
               + "                         material Material { "
               + WriterData.endl
               + "                             ambientIntensity 0.3 "
               + WriterData.endl
               + "                             diffuseColor IS diffuseColor "
               + WriterData.endl
               + "                         } "
               + WriterData.endl
               + "                     } "
               + WriterData.endl
               + "                 } "
               + WriterData.endl
               + "             ] "
               + WriterData.endl
               + "         } "
               + WriterData.endl
               + "         Billboard { "
               + WriterData.endl
               + "            axisOfRotation 0 0 0 "
               + WriterData.endl
               + "            children [ "
               + WriterData.endl
               + "                Transform { "
               + WriterData.endl
               + "                    translation IS textTranslate "
               + WriterData.endl
               + "                    children [ "
               + WriterData.endl
               + "                        Shape { "
               + WriterData.endl
               + "                            geometry DEF label0 Text { string [ \"\" ] fontStyle FontStyle { family \"SANS\" size IS textSize } }"
               + WriterData.endl
               + "                            appearance Appearance { "
               + WriterData.endl
               + "                                material Material { "
               + WriterData.endl
               + "                                    ambientIntensity 0.3 "
               + WriterData.endl
               + "                                    diffuseColor 0 0 0 "
               + WriterData.endl
               + "                                } "
               + WriterData.endl
               + "                            } "
               + WriterData.endl
               + "                        } "
               + WriterData.endl
               + "                    ] "
               + WriterData.endl
               + "                } "
               + WriterData.endl
               + "            ] "
               + WriterData.endl
               + "         } "
               + WriterData.endl
               + "         DEF script0 Script { "
               + WriterData.endl
               + "             eventIn SFBool isOver "
               + WriterData.endl
               + "             eventIn SFBool touchTime "
               + WriterData.endl
               + "             eventOut MFString trans "
               + WriterData.endl
               + "             field SFBool labelDisplayed FALSE "
               + WriterData.endl
               + "             field SFString label IS name "
               + WriterData.endl
               + "             url [ \"javascript: "
               + WriterData.endl
               + "                 function isOver(value) { "
               + WriterData.endl
               + "                     if (value) { "
               + WriterData.endl
               + "                         trans[0] = label; "
               + WriterData.endl
               + "                     } else { "
               + WriterData.endl
               + "                         if (!labelDisplayed) trans[0] = ''; "
               + WriterData.endl + "                     } " + WriterData.endl
               + "                 } " + WriterData.endl
               + "                 function touchTime() { " + WriterData.endl
               + "                     if (labelDisplayed) { "
               + WriterData.endl + "                         trans[0] = ''; "
               + WriterData.endl
               + "                         labelDisplayed = false; "
               + WriterData.endl + "                     } else { "
               + WriterData.endl
               + "                         trans[0] = label; "
               + WriterData.endl
               + "                         labelDisplayed = true; "
               + WriterData.endl + "                     } " + WriterData.endl
               + "                 }\" " + WriterData.endl + "             ] "
               + WriterData.endl + "          } " + WriterData.endl
               + "       ] " + WriterData.endl + "    } " + WriterData.endl
               + "    ROUTE sensor0.isOver TO script0.isOver "
               + WriterData.endl
               + "    ROUTE sensor0.touchTime TO script0.touchTime "
               + WriterData.endl
               + "    ROUTE script0.trans TO label0.set_string "
               + WriterData.endl + " } " + WriterData.endl + WriterData.endl

               + "Background { skyColor .8 .8 .8 }" + WriterData.endl);

    // Body.
    writeGraphicsLayout(graph.vertices, graph.edges, size);
  }

  /*****************************************************************
   * Write graphics layout.
   * @param size  Size of output area (e.g., number of pixel).
   *****************************************************************/
  @Override
  public void writeGraphicsLayout(List<GraphVertex> pVertices,
                                  List<GraphEdge> pEdges, int size) {
    float xPosMin = 1000000;
    float xPosMax = -1000000;
    float yPosMin = 1000000;
    float yPosMax = -1000000;
    float zPosMin = 1000000;
    float zPosMax = -1000000;
    for (GraphVertex lCurrVertex : graph.vertices) {
      if (lCurrVertex.showVertex) {
        xPosMin = Math.min(xPosMin, lCurrVertex.pos.x);
        xPosMax = Math.max(xPosMax, lCurrVertex.pos.x);
        yPosMin = Math.min(yPosMin, lCurrVertex.pos.y);
        yPosMax = Math.max(yPosMax, lCurrVertex.pos.y);
        zPosMin = Math.min(zPosMin, lCurrVertex.pos.z);
        zPosMax = Math.max(zPosMax, lCurrVertex.pos.z);
      }
    }

    float layoutDist;
    layoutDist = Math.max(xPosMax - xPosMin, yPosMax - yPosMin);
    layoutDist = Math.max(layoutDist, zPosMax - zPosMin);
    float xOffset = -xPosMin + 0.05f * layoutDist;
    float yOffset = -yPosMin + 0.05f * layoutDist;
    float zOffset = -zPosMin + 0.05f * layoutDist;
    float scale = 0.9f * size / layoutDist;

    int x = (int) (((xPosMin + xPosMax) / 2 + xOffset) * scale);
    int y = (int) (((yPosMin + yPosMax) / 2 + yOffset) * -scale + size);
    int z =
            (int) ((zPosMax
                    + Math.max((xPosMax - xPosMin), (yPosMax - yPosMin)) * 1.3 + zOffset) * scale);

    out.print("Viewpoint { " + WriterData.endl + "    position  " + x + " " + y
              + " " + z + WriterData.endl + "    orientation  0  0  1  0 "
              + WriterData.endl + "    description \"origin\" "
              + WriterData.endl + "} " + WriterData.endl + WriterData.endl);

    // Draw the edges.
    if (Option.showEdges.getBool() && !graph.edges.isEmpty()) {
      int end = graph.edges.size();
      for (int i = 0; i < end; ++i) {
        GraphEdge e = graph.edges.get(i);
        writeEdge(e, (int) ((graph.vertices.get(e.x).pos.x + xOffset) * scale),
            (int) ((graph.vertices.get(e.x).pos.y + yOffset) * -scale + size),
            (int) ((graph.vertices.get(e.x).pos.z + zOffset) * scale),
            (int) ((graph.vertices.get(e.y).pos.x + xOffset) * scale),
            (int) ((graph.vertices.get(e.y).pos.y + yOffset) * -scale + size),
            (int) ((graph.vertices.get(e.y).pos.z + zOffset) * scale));
      }
    }

    // Draw the vertices.
    for (GraphVertex lCurrVertex : graph.vertices) {
      if (!lCurrVertex.showName) {
        if (lCurrVertex.showVertex) {
          int radius =
                       (int) Math.max(Math.pow(lCurrVertex.degree, 0.5)
                                      * Option.minVert.getInt(), Option.minVert
                           .getInt());
          int xPos = (int) ((lCurrVertex.pos.x + xOffset) * scale);
          int yPos = (int) ((lCurrVertex.pos.y + yOffset) * -scale + size);
          int zPos = (int) ((lCurrVertex.pos.z + zOffset) * scale);

          writeVertex(lCurrVertex, xPos, yPos, zPos, radius);
        }
      }
    }

    // Draw the annotated vertices.
    for (GraphVertex lCurrVertex : graph.vertices) {
      if (lCurrVertex.showName) {
        if (lCurrVertex.showVertex) {
          int dia =
                    (int) Math.max(Math.pow(lCurrVertex.degree, 0.5)
                                   * Option.minVert.getInt(), Option.minVert
                        .getInt());
          int xPos = (int) ((lCurrVertex.pos.x + xOffset) * scale);
          int yPos = (int) ((lCurrVertex.pos.y + yOffset) * -scale + size);
          int zPos = (int) ((lCurrVertex.pos.z + zOffset) * scale);

          writeVertex(lCurrVertex, xPos, yPos, zPos, dia);
        }
      }
    }

  }

  /**
   * Writes a vertex in VRML format.
   * @param curVertex  The vertex object, to access vertex attributes.
   * @param xPos       x coordinate of the vertex.
   * @param yPos       y coordinate of the vertex.
   * @param zPos       z coordinate of the vertex.
   * @param radius     Radius of the vertex.
   */
  @Override
  public void writeVertex(GraphVertex curVertex, int xPos, int yPos, int zPos,
                          int radius) {
    String name = curVertex.name;
    //remove double quote
    if (name.startsWith("\"") && name.endsWith("\"")) {
      name = name.substring(1, name.length() - 1);
    }

    out.print("TRANSFORM_Sphere { translation " + xPos + " " + yPos + " "
              + zPos + " " + "scale " + 2 * radius + " " + 2 * radius + " "
              + 2 * radius + " " + "diffuseColor " + curVertex.color.getRed()
              + " " + curVertex.color.getGreen() + " "
              + curVertex.color.getBlue() + " " + "name " + "\"" + name
              + "\" textTranslate " + 2 * radius + " "
              + -Option.fontSize.getInt() / 2 + " " + 2 * radius + " } "
              + WriterData.endl);
  }

  /**
   * Writes an edge.
   * @param edge        an edge in graph.edges
   * @param xPos1       x coordinate of the first point.
   * @param yPos1       y coordinate of the first point.
   * @param zPos1       z coordinate of the first point.
   * @param xPos2       x coordinate of the second point.
   * @param yPos2       y coordinate of the second point.
   * @param zPos2       z coordinate of the second point.
   */
  @Override
  public void writeEdge(GraphEdge edge, int xPos1, int yPos1, int zPos1,
                        int xPos2, int yPos2, int zPos2) {

    String edgeName = edge.relName;

    int dx = xPos2 - xPos1;
    int dy = yPos2 - yPos1;
    int dz = zPos2 - zPos1;
    float dist = (float) Math.sqrt(dx * dx + dy * dy + dz * dz);
    // It does not make sense to draw an edge of length zero.
    if (dist == 0) { return; }

    /*
    float rotx = dy; 
    float roty = -dx;
    float rotz = 0.0f;
     */
    float rotx = dz;
    float roty = 0.0f;
    float rotz = -dx;

    float norm = (float) Math.sqrt(rotx * rotx + roty * roty + rotz * rotz);
    float rotw = (float) Math.asin(norm / dist);

    if (dy < 0) {
      rotw = -rotw;
    }

    out.print("TRANSFORM_Cylinder { translation " + (xPos1 + xPos2) / 2 + " "
              + (yPos1 + yPos2) / 2 + " " + (zPos1 + zPos2) / 2 + " "
              + "rotation " + rotx + " " + roty + " " + rotz + " " + rotw + " "
              + "height " + dist + " " + "diffuseColor 0 0 0 " + "name " + "\""
              + edgeName + "\" " + "textTranslate 0 0 "
              + Option.fontSize.getInt() + " } " + WriterData.endl);
  }

};
